#ifndef GENERIK_PFSSTREAM_H
#define GENERIK_PFSSTREAM_H

#include <fstream>

/*
= Overview =

PhysicsFS allows us to map several locations of files into a seamless structure,
so that we can gain access to those locations without having to know their precise
locations.

It uses the paradigm of separating the specification of locations from the 
structured filenames.

= How to wrap it =

Start by using a structure which is a global? singleton? or simply an interface?
Or, every time we "instantiate" an object of generik::pfs it simply instantiates
a new object, reusing the (in effect) global PhysicsFS functionality.

namespace pfs
{
	namespace searchpath
	{
		push_back(std::string path)
		push_front(std::string path)
		remove(std::string path)
		sane()
		
	};
	namespace writepath
	{
		set(std::string path)
		mkdir(std::string path)
		delete(std::string path)
	};

	struct file
	{
		pfsfile(const char* filename, int openmode)
		read()
		write()
		tell()
		seek(int offset, int direction)
		
	};

	read(pfsfile, void* buffer, int size)
	write(pfsfile, void* buffer, int size)
	tell(pfsfile)
	seek(pfsfile)
	// etc...
}
*/

namespace generik
{
	namespace pfs
	{
		inline void check_and_throw(int res)
		{
			if (res != 0)
			{
				throw std::runtime_error(PHYSFS_getLastError());
			}
		}
		
		namespace searchpath
		{
			void remove(const char* path)
			{
				int res = PHYSFS_removeFromSearchPath(path);
				check_and_throw(res);
			}
			
			void mount(const char* path, const char* point, int append)
			{
				int res = PHYSFS_mount(path, point, append);
				check_and_throw(res);
			}
			
			void mount(const char* path)
			{
				mount(path, 0, 1);
			}
			void mount(const char* path, const char* point)
			{
				mount(path, point, 1);
			}
			
			void mount_front(const char* path)
			{
				mount(path, 0, 0);
			}
			void mount_front(const char* path, const char* point)
			{
				mount(path, point, 0);
			}
			
		}
		
		namespace writepath
		{
			void set(const char* path)
			{
				int res = PHYSFS_setWriteDir(path);
				check_and_throw(res);
			}
			std::string get()
			{
				return PHYSFS_getWriteDir();
			}
			
			void mkdir(const char* path)
			{
				int res = PHYSFS_mkdir(path);
				check_and_throw(res);
			}
			void remove(const char* path)
			{
				int res = PHYSFS_delete(path);
				check_and_throw(res);
			}
		}
		
		typedef enum
		{
			output = -1,
			append =  0,
			input  =  1
		}
		openmode;
		
		struct file
		{
			PHYSFS_File* _handle;
			
			file(const char* filename, openmode mode)
			: _handle(0)
			{
				if (mode == input)
				{
					_handle = PHYSFS_openRead(filename);
				}
				else
				if (mode == output)
				{
					_handle = PHYSFS_openWrite(filename);
				}
				else
				// (mode == append)
				{
					_handle = PHYSFS_openAppend(filename);
				}
				check_and_throw((int)handle);
			}
			
			int write(void* buffer, int size)
			{
				int res = PHYSFS_write(_handle, buffer, size, 1);
				check_and_throw(size-res);
			}
			int read(void* buffer, int size)
			{
				int res = PHYSFS_read(_handle, buffer, size, 1);
				check_and_throw(res != -1);
				return res;
			}
			void close()
			{
				f
		};
		
		file open(
		int write(file& f, void* buffer, int size)
		{
			return f.write(buffer, size);
		}
		int read(file& f, void* buffer, int size)
		{
			return f.read(buffer, size);
		}
		
		/* pfsbuffer
		This is the buffer which holds a physfs file pointer, and provides
		access to it via the overflow and underflow methods.
		
		Implementation is taking from the example
		"An example of how to implement a custom class that inherits from std::streambuf"
		at http://www.dreamincode.net/code/snippet2499.htm
		*/
		template <class charT, class traits = std::char_traits<charT> >
		struct basic_pfsbuffer: public std::basic_streambuf<charT, traits>
		{
			/*
			These are standard basic typedef definitions.
			*/
			typedef charT                          char_type;
			typedef traits                         traits_type;
			typedef typename traits_type::int_type int_type;
			typedef typename traits_type::off_type off_type;
			typedef typename traits_type::pos_type pos_type;
			
			basic_pfsbuffer(const char* filename, int openmode)
			: _input_buffer(new charT[BUFFERSIZE])
			, _output_buffer(new charT[BUFFERSIZE])
			{
				if (openmode > 0)
				{
					// open for writing
					_file = PHYSFS_openWrite(filename);
				}
				else
				if (openmode < 0)
				{
					// open for reading
					_file = PHYSFS_openRead(filename);
				}
				else // openmode == 0
				{
					// open for appending output
					_file = PHYSFS_openAppend(filename);
				}
				
				// Initialize get pointer.  
				// This should be zero so that underflow is called upon first read.
				init_g();
				//this->setg(0, 0, 0);
				
				// Initialize the put pointer. 
				// Overflow won't get called until this buffer is filled up, so we need to use valid pointers.
				init_p();
				//this->setp(_output_buffer, _output_buffer + BUFF_SIZE - 1);
			}
			
			~basic_pfsbuffer()
			{
				delete [] _input_buffer;
				delete [] _output_buffer;
				PHYSFS_close(_file);
			}
			
		private:
			
			void init_g()
			{
				this->setg(0, 0, 0);
			}
			void init_g(std::streamsize len)
			{
				this->setg(_input_buffer, _input_buffer, _input_buffer + len);
			}
			void init_p()
			{
				this->setp(_output_buffer, _output_buffer + BUFF_SIZE - 1);
			}
			
			// the size of our storage buffer
			static const size_t BUFFERSIZE = 1024;
			// input buffer
			charT* _input_buffer;
			// output buffer
			charT* _output_buffer;
			// the Physfs output filehandle
			PHYSFS_File* _file;
			
		protected:
			// This is called when there are too many characters in the buffer (thus, a write needs to be performed).
			int_type overflow(int_type c = traits_type::eof());
			// This is called when the buffer needs to be flushed.
			int_type sync();
			// This is called when there are no more characters left in the buffer (reads a character).
			int_type underflow(int_type c = traits_type::eof());
		
		};
		
		// This function is called when the output buffer is filled.
		// In this function, the buffer should be written to wherever it should
		// be written to (in this case, the streambuf object that this is controlling).
		template <typename charT, typename traits>
		typename basic_pfsbuffer<charT, traits>::int_type
		basic_pfsbuffer<charT, traits>::overflow(basic_xor_filter<charT, traits>::int_type c)
		{
			charT* ibegin = this->_output_buffer;
			charT* iend = this->pptr();
			
			// Reset the put pointers to indicate that the buffer is free
			// (at least it will be at the end of this function).
			//setp(out_buf_, out_buf_ + BUFF_SIZE + 1);
			init_p();
			
			// If this is the end, add an eof character to the buffer.
			// This is why the pointers passed to setp are off by 1 
			// (to reserve room for this).
			if (!traits_type::eq_int_type(c, traits_type::eof()))
			{
				*iend++ = traits_type::to_char_type(c);
			}
			
			// Compute the write length.
			int_type ilen = iend - ibegin;
			
			// Write it using the output method of the object (in this case the _file) in our control.
			PHYSFS_sint64 count = PHYSFS_write(_file, _output_buffer, ilen, sizeof(charT));
			// count reports the number of charT's written!
			if (count == -1)
			{
				// complete failure during write.
				throw std::runtime_error(PHYSFS_getLastError());
			}
			if (count < ilen)
			{
				// some kind of failure during write.
				throw std::runtime_error(PHYSFS_getLastError());
			}
			
			return traits_type::not_eof(c);
		}
		
		// This is called to flush the buffer.
		// This is called when we're done with the file stream (or when .flush() is called).
		template <typename charT, typename traits>
		typename basic_pfsbuffer<charT, traits>::int_type
		basic_pfsbuffer<charT, traits>::sync()
		{
			return traits_type::eq_int_type(this->overflow(traits_type::eof()),
			traits_type::eof()) ? -1 : 0;
		}
		
		// Fill the input buffer.  This reads from the streambuf and 
		// decrypts the contents by xoring it.
		template <typename charT, typename traits>
		typename basic_pfsbuffer<charT, traits>::int_type
		basic_pfsbuffer<charT, traits>::underflow()
		{
			// Read enough bytes to fill the buffer, using the input method of our object
			PHYSFS_sint64 count = PHYSFS_read(_file, _input_buffer, BUFFERSIZE, sizeof(charT));
			
			if (count == -1)
			{
				throw std::runtime_error(PHYSFS_getLastError());
			}
			
			// Since the input buffer content is now valid (or is new)
			// the get pointer should be initialized (or reset).
			//setg(_input_buffer, _input_buffer, _input_buffer + len);
			std::streamsize len = count;
			init_g(len);
			
			// If nothing was read, then the end is here.
			if(len == 0 || PHYSFS_eof(_file)) return traits_type::eof();
			
			// Return the first character.
			return traits_type::not_eof(_input_buffer[0]);
		}
	
	}
}

#endif


